import type { ColumnOptions, EntityOptions, JoinColumnOptions, JoinTableOptions, RelationOptions } from 'typeorm';
import type { ProFormColumnsType, ProColumnType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components';
import type { CrudFilters, MetaQuery } from '@refinedev/core';
import type { Rule } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface';
export const Tree = (name: string): any => {
    return (target: any) => {
        Reflect.set(target, 'isTree', true)
    }
}
export const TreeParentMetadataKey = `TreeParentMetadataKey`
export const TreeParent = (options: any = {}): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, TreeParentMetadataKey) || [];
        columns.push({
            options,
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, TreeParentMetadataKey, columns)
    }
}
export const UniqueMetadataKey = `UniqueMetadataKey`
export const Unique = (name: string, fields: string[], options?: any): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, UniqueMetadataKey) || [];
        columns.push({
            options: {
                ...options,
                fields,
                name,
            },
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, UniqueMetadataKey, columns)
    }
}
export const ManyToOneMetadataKey = `ManyToOneMetadataKey`
export const ManyToOne = <T>(factory: () => any, inverseSide: string, options?: RelationOptions & { label?: string }): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, ManyToOneMetadataKey) || [];
        columns.push({
            options: {
                ...options,
                factory
            },
            inverseSide,
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, ManyToOneMetadataKey, columns)
    }
}
export const OneToOneMetadataKey = `OneToOneMetadataKey`
export const OneToOne = (factory: () => any, inverseSide: string, options?: any): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, OneToOneMetadataKey) || [];
        columns.push({
            options: {
                ...options,
                factory,
            },
            inverseSide,
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, OneToOneMetadataKey, columns)
    }
}
export const OneToManyMetadataKey = `OneToManyMetadataKey`
export const OneToMany = <T>(factory: () => T, inverseSide: string, options?: any): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, OneToManyMetadataKey) || [];
        columns.push({
            options: {
                ...options,
                factory,
            },
            inverseSide,
            target: target.constructor,
            name: key as any
        });
        Reflect.set(target.constructor, OneToManyMetadataKey, columns)
    }
}
export const ManyToManyMetadataKey = `ManyToManyMetadataKey`
export const ManyToMany = <T>(factory: () => T, inverseSide: string, options?: any): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, ManyToManyMetadataKey) || [];
        columns.push({
            options: { ...options, factory },
            target: target.constructor,
            inverseSide,
            name: key as any
        })
        Reflect.set(target.constructor, ManyToManyMetadataKey, columns)
    }
}
export const JoinColumnManyMetadataKey = `JoinColumnManyMetadataKey`
export const JoinColumn = (options: JoinColumnOptions & { label?: string }): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, JoinColumnManyMetadataKey) || [];
        columns.push({
            options,
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, JoinColumnManyMetadataKey, columns)
    }
}
export const JoinTableManyMetadataKey = `JoinTableManyMetadataKey`
export const JoinTable = (options: JoinTableOptions): any => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, JoinTableManyMetadataKey) || [];
        columns.push({
            options,
            target: target.constructor,
            name: key as any
        })
        Reflect.set(target.constructor, JoinTableManyMetadataKey, columns)
    }
}
export type IColumn = Partial<IIColumn>
type IIColumn = {
    toFilter?: (val: any) => CrudFilters;
    toSearch?: (val: any) => CrudFilters;
    direction?: 'horizontal' | 'vertical';
    displayorder?: boolean;
    buttons?: (ids: any[], rows: any[], reload: any)=>any[];
    isEdit?: boolean;
    isCreate?: boolean;
    hide?: boolean;
    options?: any;
    target?: any;
    label?: string;
    placeholder?: string;
    tooltip?: ProFormColumnsType['tooltip'];
    primaryType?: string;
    hideInEdit?: boolean;
    hideInCreate?: boolean;
    showInImport?: boolean;
    isImportFile?: boolean;// 是否上传数据
    generate?: (item: any) => any;
    span?: number;
    select?: any;
    skip?: boolean;
    rules?: Rule[];
    showInSearch?: boolean;
    renderSearch?: ProFormColumnsType['renderFormItem'];
    name: NamePath | NamePath[];

    isTitle?: boolean;
    isSubtitle?: boolean;
    isType?: boolean;
    isAvatar?: boolean;
    isContent?: boolean;
    isAction?: boolean;
    handler?: Function;
} & Omit<ColumnOptions, 'name'>
    & Pick<ProFormColumnsType, 'render' | 'renderFormItem' | 'renderText' |
        'valueType' | 'hideInTable' | 'hideInDescriptions' | 'proFieldProps' |
        'fieldProps' | 'formItemProps' | 'colProps' | 'colSize' | 'dataIndex' |
        'debounceTime' | 'valueEnum' | 'editable' | 'ignoreFormItem' | 'key' |
        'hideInForm' | 'dependencies' | 'transform' | 'columns' | "rowProps" | 'width' | 'convertValue'
    >
    & Pick<ProColumnType, 'index' | 'copyable' | 'ellipsis' | 'fixed'
        | 'sorter' | 'sortOrder' | 'filterDropdown' | 'filters'
        | 'filterDropdownOpen' | 'onFilterDropdownOpenChange' | 'onFilter' | 'filterSearch'
        | 'filterResetToDefaultFilteredValue' | 'filterMultiple' | 'filterIcon' | 'filterMode'
        | 'filteredValue' | 'filtered' | 'title'>;

export interface Sorters {
    [key: string]: 'asc' | 'desc'
}
export type SortOrder = 'descend' | 'ascend' | null;

const staticResourceMap: Map<string, any> = new Map()
export function getTargetByName(name: string): any {
    return staticResourceMap.get(name)
}
export function setTargetByName(name: string, target: any): any {
    return staticResourceMap.set(name, target)
}
export const EntityMetadataKey = `EntityMetadataKey`
export const Entity = (options: EntityOptions & { title?: string, comment?: string, cardActionProps?: 'extra' | 'actions'; }): ClassDecorator => {
    return (target: any) => {
        const name = options.name || target.name
        Reflect.set(target, EntityMetadataKey, options)
        Reflect.set(target, 'resource', name)
        Reflect.set(target, 'title', options.comment)
        Reflect.set(target, 'cardActionProps', options.cardActionProps)
        staticResourceMap.set(name, target)
    }
}
import type { ProListMetas, ProListMeta } from '@ant-design/pro-list';
export const toProListMeta = (column: IColumn): ProListMeta<any> => {
    return {
        dataIndex: column.dataIndex as any,
        valueType: column.valueType,
        render: column.render as any,
        title: column.title,
        valueEnum: column.valueEnum,
        editable: column.editable,
        fieldProps: column.fieldProps,
        formItemProps: column.formItemProps  as any,
        renderFormItem: column.renderFormItem  as any,
        search: false
    }
}
export const getProListMetas = (target: any): ProListMetas<any> => {
    const cardActionProps: any = Reflect.get(target, 'cardActionProps')
    const columns = getTargetColumns(target);
    // todo : prolist metadata
    const list: ProListMetas<any> = {
        actions: {
            cardActionProps
        } as any
    }
    const title = columns.find(it => !!it.isTitle)
    if (title) {
        list.title = toProListMeta(title)
    }
    const avatar = columns.find(it => !!it.isAvatar)
    if (avatar) {
        list.avatar = toProListMeta(avatar)
    }
    const content = columns.find(it => !!it.isContent)
    if (content) {
        list.content = toProListMeta(content)
    }
    const subTitle = columns.find(it => !!it.isSubtitle)
    if (subTitle) {
        list.subTitle = toProListMeta(subTitle)
    }
    const type = columns.find(it => !!it.isType)
    if (type) {
        list.type = toProListMeta(type)
    }
    return list;
}
export const ColumnMetadataKey = `ColumnMetadataKey`
export const Column = (options: IColumn): PropertyDecorator => {
    return (target: any, key: any) => {
        const columns: IColumn[] = Reflect.get(target.constructor, ColumnMetadataKey) || [];
        if (!!options.primary) {
            Reflect.set(target.constructor, 'primary', key)
            Reflect.set(target.constructor, 'primaryType', options.primaryType || 'uuid')
        }
        if (!!options.displayorder) {
            Reflect.set(target.constructor, 'displayorder', key)
        }
        columns.push({
            target: target.constructor,
            name: key as any,
            dataIndex: key as any,
            ...options,
        });
        Reflect.set(target.constructor, ColumnMetadataKey, columns)
    }
}
export const KeywordColumnMetadataKey = `KeywordColumnMetadataKey`
export function Keyword(): PropertyDecorator {
    return (target, key) => {
        const columns = Reflect.get(target.constructor, KeywordColumnMetadataKey) || [];
        columns.push({
            name: key as any,
            target: target.constructor,
        })
        Reflect.set(target.constructor, KeywordColumnMetadataKey, columns)
    }
}

export function transformerTableColumn(column: IColumn) {
    column.label = column.label || column.comment;
    return {
        key: column.key,
        fixed: column.fixed,
        ellipsis: column.ellipsis,
        copyable: column.copyable,
        name: column.name,
        title: column.title || column.label,
        dataIndex: column.dataIndex || column.name,
        valueType: column.valueType,
        formItemProps: column.formItemProps,
        fieldProps: column.fieldProps,
        render: column.render,
        renderFormItem: column.renderFormItem,
        renderText: column.renderText,
        tooltip: column.tooltip,
        valueEnum: column.valueEnum,
        editable: column.editable,
        debounceTime: column.debounceTime,
        ignoreFormItem: column.ignoreFormItem,
        hideInDescriptions: column.hideInDescriptions,
        hideInForm: column.hideInForm,
        hideInTable: column.hideInTable,
        hideInSearch: !!column.showInSearch ? false : true,
        proFieldProps: column.proFieldProps,
        sorter: column.sorter,
        filters: column.filters,
        filterDropdown: column.filterDropdown,
        filterDropdownOpen: column.filterDropdownOpen,
        filtered: column.filtered,
        filteredValue: column.filteredValue,
        filterIcon: column.filterIcon,
        filterMode: column.filterMode,
        filterMultiple: column.filterMultiple,
        filterResetToDefaultFilteredValue: column.filterResetToDefaultFilteredValue,
        filterSearch: column.filterSearch,
        onFilter: column.onFilter,
        onFilterDropdownOpenChange: column.onFilterDropdownOpenChange
    } as ProColumns<any, any>
}
export function getTargetColumns(target: any): IColumn[] {
    return Reflect.get(target, ColumnMetadataKey) || [];
}
export function getCreateTargetColumns(target: any): IColumn[] {
    return getTargetColumns(target).filter(it => !it.hideInCreate).map(it => {
        return { ...it, isCreate: true }
    })
}
export function getDefaultTargetColumns(target: any, initValue: any = {}): any {
    return getTargetColumns(target).map(it => {
        if (typeof it.default === 'function') {
            return { [`${it.name}`]: get(initValue, `${it.name}`, it.default()) }
        }
        return { [`${it.name}`]: get(initValue, `${it.name}`, it.default) }
    }).reduce((a, b) => ({ ...a, ...b }), {})
}

export function get(target: any, key: string, def: any) {
    return Reflect.has(target, key) ? Reflect.get(target, key) : def;
}
export function getEditTargetColumns(target: any): IColumn[] {
    return getTargetColumns(target).filter(it => !it.hideInEdit).map(it => {
        return { ...it, isEdit: true }
    })
}
export function getShowTargetColumns(target: any): IColumn[] {
    return getTargetColumns(target).filter(it => !it.hideInDescriptions).map(it => {
        return { ...it, isShow: true }
    })
}
export function transformerShowColumn(column: IColumn): any {
    return {
        ...column,
        title: column.title || column.label,
        dataIndex: column.dataIndex || column.name,
    } as ProDescriptionsItemProps
}
export function transformerSchemaColumn(column: IColumn, initValue: any = {}): ProFormColumnsType {
    column.label = column.label || column.comment
    const { fieldProps, placeholder, rules, nullable, formItemProps, ...otherProps } = column;
    const _rules = rules || [];
    if (!nullable) {
        _rules.push({ required: true, message: `${column.label}必填` })
    }
    return {
        key: column.key || column.dataIndex || column.name || column.label,
        label: column.label || column.title || column.comment,
        title: column.title || column.label || column.comment,
        dataIndex: column.dataIndex || column.name,
        transform: column.transform,
        formItemProps: {
            rules: _rules,
            hidden: !!column.hide,
            ...formItemProps
        },
        fieldProps: {
            hidden: !!column.hide,
            placeholder: placeholder || `请输入${column.label}`,
            required: !column.nullable,
            ...fieldProps,
        },
        ...otherProps
    } as ProFormColumnsType
}
export function createTargetCreateMetaQuery(target: any): MetaQuery {
    const columns = getCreateTargetColumns(target)
    return {
        fields: columns.map(c => {
            const fields: any[] = [];
            if (!c.skip) {
                const { dataIndex, valueType } = c;
                if (dataIndex && valueType !== 'option') {
                    fields.push(dataIndex)
                }
            }
            return fields
        }).flat().filter(it => !!it) as any[]
    }
}
export function createTargetMetaQuery(target: any): MetaQuery {
    const columns = getTargetColumns(target)
    return {
        fields: columns.map(c => {
            const fields: any[] = [];
            const select = c.select;
            if (select) {
                if (Array.isArray(select)) {
                    fields.push(...select)
                } else {
                    fields.push(select)
                }
            }
            if (!c.skip) {
                const { dataIndex, valueType } = c;
                if (dataIndex && valueType !== 'option') {
                    fields.push(dataIndex)
                }
            }
            return fields
        }).flat().filter(it => !!it) as any[]
    }
}
export function createTargetMetaFields(target: any) {
    const columns = getTargetColumns(target)
    return columns.map(c => {
        const fields: any[] = [];
        const select = c.select;
        if (select) {
            if (Array.isArray(select)) {
                fields.push(...select)
            } else {
                fields.push(select)
            }
        }
        if (!c.skip) {
            const { dataIndex, valueType } = c;
            if (dataIndex && valueType !== 'option') {
                fields.push(dataIndex)
            }
        }
        return fields
    }).flat().filter(it => !!it) as any[]
}
const elements: Set<any> = new Set();
interface ElementOptions {
    name: string;
    title: string;
    preview?: string;
    desc?: string;
    mark?: string;
}
export const ElementMetadataKey = `ElementMetadataKey`;
export const Element = (options: ElementOptions): ClassDecorator => {
    return (type: any) => {
        Reflect.set(type, ElementMetadataKey, options);
        elements.add(type);
    }
}
export function getElementOptions(entity: any): ElementOptions {
    const options = Reflect.get(entity, ElementMetadataKey)
    return options;
}
export const InputMetadataKey = `InputMetadataKey`
export const Input = (options: IColumn): PropertyDecorator => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, InputMetadataKey) || [];
        columns.push({
            target: target.constructor,
            name: key as any,
            dataIndex: key as any,
            nullable: true,
            ...options,
        });
        Reflect.set(target.constructor, InputMetadataKey, columns)
    }
}
export const getElementInput = (target: any): IColumn[] => {
    return Reflect.get(target, InputMetadataKey)
}
export const OutputMetadataKey = `InputMetadataKey`
export interface OutputOptions {
    title: string;
}
export const Output = (options: OutputOptions): PropertyDecorator => {
    return (target: any, key: any) => {
        const columns: any[] = Reflect.get(target.constructor, OutputMetadataKey) || [];
        columns.push({
            target: target.constructor,
            name: key as any,
            ...options,
        });
        Reflect.set(target.constructor, OutputMetadataKey, columns)
    }
}
export const getRelationColumns = (target: any, key: string) => {
    const manyToOnes = Reflect.get(target, key);
    const relations: any[] = [];
    if (manyToOnes && manyToOnes.length > 0) {
        manyToOnes.map((manyToOne: any) => {
            const { name, options, target: fromTarget, inverseSide } = manyToOne;
            const { factory, label } = options;
            const toTarget = factory();
            const from = Reflect.get(fromTarget, EntityMetadataKey)
            const to = Reflect.get(toTarget, EntityMetadataKey)
            const joinColumns = Reflect.get(fromTarget, JoinColumnManyMetadataKey)
            if (!joinColumns) return;
            const column = joinColumns.find((it: any) => it.name === name)
            if (!inverseSide) {
                console.log(`${key} ${from.name} ${name} is null`)
                return;
            }
            if (!column) {
                console.log(`join column not found`)
                return;
            }
            relations.push({
                type: key,
                name: name,
                from_name: from.name,
                from_column_name: column.options.name,
                to_name: to.name,
                to_column_name: inverseSide,
                title: label || ''
            })
        })
    }
    return relations;
}
export const getManyToOneColumns = (target: any) => {
    return getRelationColumns(target, ManyToOneMetadataKey)
}

export const getOneToManyColumns = (target: any) => {
    return getRelationColumns(target, OneToManyMetadataKey)
}

export const getOneToOneColumns = (target: any) => {
    return getRelationColumns(target, OneToOneMetadataKey)
}

export const getManyToManyColumns = (target: any) => {
    return getRelationColumns(target, ManyToManyMetadataKey)
}
